#ifdef __linux__
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <pthread.h>
#endif

#include <cstring>
#include <iostream>
#include <list>

#include <boost/foreach.hpp>
#include <boost/function.hpp>
#include <boost/thread.hpp>

#include <Common/Debug.hh>
#include "Common/Logging.hh"
#include "Common/AutoSingleton.hh"

struct Task
{
	typedef boost::shared_ptr< Task > Ptr;

	virtual void operator()() = 0;
};

class Foobar1Task : public Task
{
	public:
		virtual void operator()() { L_ << "foobar1() thread"; throw std::runtime_error( "Forced failure!" ); }
};

class Foobar2Task : public Task
{
	public:
		virtual void operator()() { L_ << "foobar2() thread"; int i = 0; BOOST_ASSERT( i != 0 ); }
};

class TaskScheduler
{
	public:
		typedef boost::shared_ptr< TaskScheduler > Ptr;
		typedef std::list< Task::Ptr > TaskList;

	private:
		int id;

		TaskList iTasks;

	public:
		TaskScheduler( int _id );
		~TaskScheduler();

		void addTask( const Task::Ptr &aTask ) { iTasks.push_back( aTask ); }

		void operator()();
};

TaskScheduler::TaskScheduler( int _id )
	: id( _id )
{
	L_ << "Creating TaskScheduler " << id;
}

TaskScheduler::~TaskScheduler()
{
	L_ << "Destroying TaskScheduler" << id;
}

void TaskScheduler::operator()()
{
#ifdef __linux__
	pthread_t thread = pthread_self();

	cpu_set_t cpuset;

	CPU_ZERO( &cpuset );
	CPU_SET( id, &cpuset );

	int err = pthread_setaffinity_np( thread, sizeof( cpu_set_t ), &cpuset );

	if ( err != 0 )
   	{
		char str[ 256 ];

		L_ << strerror_r( err, str, sizeof( str ) );
	}
	else
	{
		L_ << "Thread " << std::hex << thread << " is running on CPU " << id << ".";
	}
#endif

	L_ << "This thread: " << boost::this_thread::get_id();

	BOOST_FOREACH( Task::Ptr &t, iTasks )
	{
		try
		{
			(*t)();
		}
		catch ( const std::exception &ex )
		{
			L_ << "Exception caught: " << Common::Debug::demangle( typeid( ex ).name() );
			L_ << ex.what();
		}
		catch ( ... )
		{
			L_ << "Oops: Unknown exception!";
		}
	}
}

class GlobalScheduler : public Common::AutoSingleton< GlobalScheduler >
{
	private:
		std::vector< TaskScheduler::Ptr > iTaskSchedulers;

		boost::thread_group iThreads;

		void _run( );

	public:
		GlobalScheduler( );

		void addTask( int num, const Task::Ptr &aTask ) { iTaskSchedulers[ num ]->addTask( aTask ); };

		static void run( ) { GlobalScheduler::getRef()._run(); };
};

GlobalScheduler::GlobalScheduler()
{
	L_ << "hardware concurrency: " << boost::thread::hardware_concurrency();

	for ( uint32_t i = 0; i < boost::thread::hardware_concurrency(); i++ )
	{
		iTaskSchedulers.push_back( TaskScheduler::Ptr( new TaskScheduler( i ) ) );
	}
}

void GlobalScheduler::_run()
{
	L_ << "spawning threads";

	BOOST_FOREACH( TaskScheduler::Ptr &scheduler, iTaskSchedulers )
	{
		iThreads.create_thread( boost::bind( &TaskScheduler::operator(), scheduler ) );
	}

	L_ << "joining on threads";

	iThreads.join_all();
}

int main( int /* argc */, char ** /* argv */ )
{
	Common::Logger::initialize();

	GlobalScheduler::getRef().addTask( 0, Task::Ptr( new Foobar1Task() ) );
	GlobalScheduler::getRef().addTask( 0, Task::Ptr( new Foobar2Task() ) );
	GlobalScheduler::getRef().addTask( 1, Task::Ptr( new Foobar2Task() ) );
	GlobalScheduler::getRef().addTask( 1, Task::Ptr( new Foobar1Task() ) );

	GlobalScheduler::run();

	return 0;
}
